
use log::debug;
use tokio::{
    io::{AsyncReadExt, AsyncWriteExt, BufReader},
    sync::Semaphore,
};
use tokio_serial::{new, SerialPortBuilderExt, SerialStream};

use crate::{
    error::{new_err, new_prot_err, AppErr, ErrWrap},
    utils::{get_mut, new_buf},
};

use super::proto::{xor_sum, FRAME_END, FRAME_HEAD0, FRAME_HEAD1, MIN_LEN};


pub struct SerialConn {
    stream: BufReader<SerialStream>,
    exit_sem: Semaphore,
}

impl Drop for SerialConn {

    fn drop(&mut self) {
        debug!("serial port delete");
    }
}

impl SerialConn {
    
    pub fn new(name: &str, baud_rate: u32) -> Result<Self, AppErr> {
        let stream = new(name, baud_rate).open_native_async().wrap()?;

        #[cfg(unix)]
        {
            use super::unix::fd_lock;
            fd_lock(&stream)?;
        }

        let conn = SerialConn {
            stream: BufReader::new(stream),
            exit_sem: Semaphore::new(0),
        };
        Ok(conn)
    }

    pub fn close(&self) {
        self.exit_sem.add_permits(1);
    }

    pub async fn write(&self, buf: &[u8]) -> Result<(), AppErr> {
        let stream = get_mut(&self.stream);
        stream.write_all(buf).await?;
        Ok(())
    }

    pub async fn read(&self) -> Result<Box<[u8]>, AppErr> {

        let stream = get_mut(&self.stream);
        loop {
            let frame = tokio::select! {
                _ = self.exit_sem.acquire() => {
                    break;
                }

                frame = read_frame(stream) => {
                    frame
                }
            };
            match frame {
                Ok(v) => return Ok(v),
                Err(AppErr::Proto(e)) => {
                    debug!("{}", e);
                },
                Err(e) => return Err(e),
            }

        }
        Err(new_err("closed"))
    }
}

async fn read_frame<R: AsyncReadExt + Unpin>(r: &mut R) -> Result<Box<[u8]>, AppErr> {
    let len = sync(r).await? - 3;
    let mut buf = new_buf(len);

    r.read_exact(&mut buf).await?;

    let s = xor_sum(&buf[..(len - 2)]);
    if buf[len - 2] != s {
        return Err(new_prot_err("xor sum fail"));
    }

    if buf[len - 1] != FRAME_END {
        return Err(new_prot_err("end fail"));
    }

    Ok(buf)
}

async fn sync<R: AsyncReadExt + Unpin>(r: &mut R) -> Result<usize, AppErr> {
    let mut buf = [0u8; 1];
    let mut flag = false;

    loop {
        r.read_exact(&mut buf).await?;
      //  debug!("read:{:02x}", buf[0]);
        if flag && (buf[0] == FRAME_HEAD1) {
            break;
        }
        flag = buf[0] == FRAME_HEAD0;
    }
    r.read_exact(&mut buf).await?;

    let len = buf[0] as usize;
    if len < MIN_LEN {
        return Err(new_prot_err("len < min len"));
    }

    Ok(len)
}
